﻿using System;
using Newtonsoft.Json;
using RabbitMQ.Client;

namespace Infrastructure.Utility.Core.RabbitMq.Providers
{
    public class Provider : IProvider
    {
        private readonly ConnectionConfiguration _connectionConfig;
        private readonly ConnectionPool _connectionPool;
        private static readonly object FreeConnLock = new object();
        private static readonly object AddConnLock = new object();
        public Provider(ConnectionConfiguration config)
        {
            _connectionConfig = config;
            _connectionPool = ConnectionPool.Create(config);
        }
        public IConnection CreateConnectionInPool()
        {
            SelectConnectionLine:
            _connectionPool.ConnectionPoolSemaphore.WaitOne();
            IConnection connection = null;
            if ((_connectionPool.FreeQueue.Count + _connectionPool.BusyDic.Count) < _connectionConfig.MaxConnectionCount)
            {
                lock (AddConnLock)
                {
                    if (_connectionPool.FreeQueue.Count + _connectionPool.BusyDic.Count < _connectionConfig.MaxConnectionCount)
                    {
                        connection = CreateConnection();
                        _connectionPool.BusyDic[connection] = true;
                        _connectionPool.UseDic[connection] = 1;
                        return connection;
                    }
                }
            }
            if (!_connectionPool.FreeQueue.TryDequeue(out connection))
            {
                goto SelectConnectionLine;
            }
            else if (_connectionPool.UseDic[connection] + 1 > _connectionConfig.MaxConnectionUsingCount || !connection.IsOpen)
            {
                CloseConnection(connection);
                connection = CreateConnection();
                _connectionPool.UseDic[connection] = 0;
            }
            _connectionPool.BusyDic[connection] = true;
            _connectionPool.UseDic[connection] = _connectionPool.UseDic[connection] + 1;
            return connection;
        }
        public void ResetConnectionToFree(IConnection connection)
        {
            lock (FreeConnLock)
            {
                if (!_connectionPool.BusyDic.TryRemove(connection, out _))
                {
                    throw new Exception("Remove connection fail");
                }
                if (_connectionPool.FreeQueue.Count + _connectionPool.BusyDic.Count > _connectionConfig.MaxConnectionCount)
                {
                    CloseConnection(connection);
                }
                else
                {
                    _connectionPool.FreeQueue.Enqueue(connection);
                }
                _connectionPool.ConnectionPoolSemaphore.Release();
            }
        }
        public IModel CreateChannel(IConnection connection)
        {
            return connection.CreateModel();
        }
        public string ToJson<T>(T model)
        {
            var settings = new JsonSerializerSettings()
            {
                NullValueHandling = NullValueHandling.Ignore
            };
            return JsonConvert.SerializeObject(model, settings);
        }
        private ConnectionFactory CreateFactory()
        {
            return new ConnectionFactory
            {
                HostName = _connectionConfig.Server,
                Port = _connectionConfig.Port,
                UserName = _connectionConfig.UserName,
                Password = _connectionConfig.Password,
                VirtualHost = _connectionConfig.VHost,
                ContinuationTimeout = TimeSpan.FromSeconds(_connectionConfig.Timeout),
                AutomaticRecoveryEnabled = true//自动重连
            };
        }
        private IConnection CreateConnection()
        {
            return CreateFactory().CreateConnection();
        }
        private void CloseConnection(IConnection connection)
        {
            if (connection != null)
            {
                if (connection.IsOpen) connection.Close();
                connection.Dispose();
            }
        }
        public string ExchangeName => _connectionConfig.Exchange;
        public string QueueName => _connectionConfig.QueueName;
        public string RouteKey => _connectionConfig.RouteKey;
        public bool Durable => _connectionConfig.Durable;
    }
}
